package top.h1234.codesandbox;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import top.h1234.codesandbox.model.ExecuteCodeRequest;
import top.h1234.codesandbox.model.ExecuteCodeResponse;
import top.h1234.codesandbox.model.ExecuteMessage;
import top.h1234.codesandbox.model.JudgeInfo;
import top.h1234.codesandbox.model.enums.JudgeInfoEnum;
import top.h1234.codesandbox.utils.ProcessUtils;

import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

/**
 * Java代码沙箱模板
 */
public abstract class JavaCodeSandboxTemplate implements CodeSandbox {

    @Override
    public ExecuteCodeResponse executeCode(ExecuteCodeRequest executeCodeRequest) {
        // 返回结果
        ExecuteCodeResponse executeCodeResponse = new ExecuteCodeResponse();

        // 1）保存代码到文件
        String code = executeCodeRequest.getCode();
        File file = SaveCodeToFile(code);

        // 2）编译代码
        ExecuteMessage executeMessage = compileFile(file);
        // 编译代码失败
        if (executeMessage.getExitValue() != 0) {
            executeCodeResponse.setStatus(2);
            executeCodeResponse.setMessage("编译失败");
            JudgeInfo judgeInfo = new JudgeInfo();
            judgeInfo.setMessage(JudgeInfoEnum.Compile_Error.getValue());
            executeCodeResponse.setJudgeInfo(judgeInfo);
            return executeCodeResponse;
        }

        // 3）执行代码
        List<String> inputCaseList = executeCodeRequest.getInputCaseList();
        List<ExecuteMessage> executeMessageList = runFile(inputCaseList, file);

        // 4）整理输出结果
        executeCodeResponse = organizeOutputResult(executeMessageList);

        // 5）文件清理
        Boolean deletedFile = deleteFile(file);
        if (!deletedFile) throw new RuntimeException("清理代码文件失败！");

        return executeCodeResponse;
    }


    /**
     * 1、保存代码
     *
     * @param code 代码
     * @return 代码文件
     */
    public File SaveCodeToFile(String code) {
        // 获取全局工作目录（D:\code\hoj-code-sandbox）
        String globalWorkDir = System.getProperty("user.dir");
        // 全局代码存放路径（D:\code\hoj-code-sandbox\tempCode）
        String globalCodePathName = globalWorkDir + File.separator + "tempCode";
        // 不存在该目录，进行创建
        if (!FileUtil.exist(globalCodePathName)) FileUtil.mkdir(globalCodePathName);
        // 隔离不同用户的代码（D:\code\hoj-code-sandbox\tempCode\81674944）
        String userCodeParentPath = globalCodePathName + File.separator + UUID.randomUUID();
        // 用户代码路径（D:\code\hoj-code-sandbox\tempCode\81674944\Main.java）
        String userCodePath = userCodeParentPath + File.separator + "Main.java";
        // 将用户代码文件进行保存
        return FileUtil.writeString(code, userCodePath, StandardCharsets.UTF_8);
    }

    /**
     * 2、编译代码
     *
     * @param file 代码文件
     */
    public ExecuteMessage compileFile(File file) {
        // 编译命令
        String compileCmd = String.format("javac -encoding utf-8 %s", file.getAbsolutePath());
        try {
            // 开始编译
            Process compileProcess = Runtime.getRuntime().exec(compileCmd);
            // 获取进程输出结果
            return ProcessUtils.getProcessOutputMessage(compileProcess, "编译");
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 3、运行代码
     *
     * @param inputCaseList 输入用例列表
     * @param file          代码文件
     */
    public List<ExecuteMessage> runFile(List<String> inputCaseList, File file) {
        // 执行结果信息列表
        List<ExecuteMessage> executeMessageList = new ArrayList<>();
        for (String inputCase : inputCaseList) {
            // 运行命令
            String runCmd = String.format("java -Dfile.encoding=UTF-8 -cp %s Main %s", file.getParentFile(), inputCase);
            try {
                // 开始运行
                Process runProcess = Runtime.getRuntime().exec(runCmd);
                // 获取运行结果
                ExecuteMessage executeMessage = ProcessUtils.getProcessOutputMessage(runProcess, "运行");
                System.out.println("运行结果信息 => " + executeMessage);
                executeMessageList.add(executeMessage);
            } catch (IOException e) {
                System.out.println(getErrorResponse(e));
                throw new RuntimeException(e);
            }
        }
        return executeMessageList;
    }

    /**
     * 4、整理输出结果
     *
     * @param executeMessageList 沙箱执行结果列表
     * @return 输出结果
     */
    public ExecuteCodeResponse organizeOutputResult(List<ExecuteMessage> executeMessageList) {
        ExecuteCodeResponse executeCodeResponse = new ExecuteCodeResponse();
        List<String> outputList = new ArrayList<>();
        long maxTime = 0;
        long maxMemory = 0;
        for (ExecuteMessage executeMessage : executeMessageList) {
            String errorMessage = executeMessage.getErrorMessage();
            // 有错误信息
            if (StrUtil.isNotBlank(errorMessage)) {
                executeCodeResponse.setMessage(errorMessage);
                // 用户代码执行中存在错误
                executeCodeResponse.setStatus(3);
                break;
            } else {
                // 正常执行
                executeCodeResponse.setStatus(1);
                String message = executeMessage.getMessage();
                outputList.add(message);
                Long time = executeMessage.getTime();
                if (time != null) maxTime = Math.max(maxTime, time);
                Long memory = executeMessage.getMemory();
                if (memory != null) maxMemory = Math.max(maxMemory, memory);
            }
        }
        executeCodeResponse.setOutputResultList(outputList);
        JudgeInfo judgeInfo = new JudgeInfo();
        judgeInfo.setTime(maxTime);
        judgeInfo.setMemory(maxMemory);
        executeCodeResponse.setJudgeInfo(judgeInfo);
        return executeCodeResponse;
    }

    /**
     * 5、代码文件清理
     *
     * @param file 代码文件
     * @return 是否删除成功
     */
    public Boolean deleteFile(File file) {
        if (file.getParentFile().exists()) {
            return FileUtil.del(file.getParentFile());
        }
        return false;
    }

    /**
     * 获取错误响应
     *
     * @param e 异常对象
     * @return 执行代码的响应体对象
     */
    public ExecuteCodeResponse getErrorResponse(Exception e) {
        ExecuteCodeResponse executeCodeResponse = new ExecuteCodeResponse();
        executeCodeResponse.setOutputResultList(new ArrayList<>());
        executeCodeResponse.setMessage(e.getMessage());
        // 表示代码沙箱错误
        executeCodeResponse.setStatus(4);
        executeCodeResponse.setJudgeInfo(new JudgeInfo());
        return executeCodeResponse;
    }
}
